/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
#ifndef __STACKMAP5_H__
#define __STACKMAP5_H__

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "../base/stackmap_x.h"

struct StackmapElement_5;
struct WorkmapElement_5;

//possible relations between verificaton types
enum ConstraintType {
    CT_GENERIC = 0,         // sub-defined type A is assignable to sub-defined type B
    CT_ARRAY2REF = 1,       // A is a known-type array. element of A is assignable to sub-defined type B
    CT_EXPECTED_TYPE = 2,   // sub-defined type A is assignable to known-type B
    CT_INCOMING_VALUE = 3   // known-type A is assignable to sub-defined type B
};

//list constant verification type (i.e. known-type) that are assignable to some sub-definite type (i.e. StackMapElement)
//see StackmapElement
struct IncomingType {
    //next in the list
    IncomingType *nxt;

    //value of the verification type recorded as int
    _SmConstant value;

    //simple next in the list
    IncomingType *next() {
        return nxt;
    }
};

//list of constraints for some sub-definite verification type (i.e. StackMapElement)
//see StackmapElement
struct Constraint {
    //next in the list
    Constraint *nxt;

    //either
    union {
        StackmapElement_5 *variable; // sub-definite verificarion type
        int value;                 // or constant (known) verification type rcorded as int
    };

    //consatrint type
    ConstraintType type;

    //next constrait of type 't'
    static Constraint *next(Constraint *cur, int t) {
        while( cur && cur->type != t ) {
            cur = (Constraint*)cur->next();
        }
        return cur;
    }

    //simple next in the list
    Constraint *next() {
        return nxt;
    }
};

//constraint of the CT_EXPECTED_TYPE type: sub-defined type A is assignable to known-type B
struct ExpectedType : Constraint {
    ExpectedType *next() {
        return (ExpectedType *) Constraint::next(Constraint::next(), CT_EXPECTED_TYPE);
    }
};

//constraint of the CT_GENERIC type: sub-defined type A is assignable to sub-defined type B
struct GenericCnstr : Constraint {
    GenericCnstr *next() {
        return (GenericCnstr *) Constraint::next(Constraint::next(), CT_GENERIC);
    }
};

//constraint of the CT_ARRAY2REF type: A is a known-type array. element of A is assignable to sub-defined type B
struct ArrayCnstr : Constraint {
    //there can be only one CT_ARRAY2REF per StackMap Element
    ArrayCnstr *next() {
        assert(0);
        return 0;
    }
};

//StackMapElement structure represens sub-definite verification type: we don't know what type is it, but
//we know about instructions that expect ExpectedTypes here and we know that IncomingValues can be here
//we also know that this type must be assignable to other sub-defenite types as indicated by CT_GENERIC
//constrains and there can be special limitations represented by CT_ARRAY2REF constraints
struct StackmapElement_5 { //TODO: should be rewritten to save footprint
    //list of IncomingType constraint
    IncomingType *incoming;

    //list of all the conatraints of other types
    Constraint *others;

    //return value from any IncomingType constraint
    //when we need to compae to some unmergable type we don;t need to interate thru the list
    //also used to assert that an IncomingValue constraint exists
    SmConstant getAnyIncomingValue() {
        assert(firstIncoming());
        return firstIncoming()->value;
    }

    //return first IncomingType constraint
    IncomingType *firstIncoming() {
        //TODO: I have to store somewhere the "modified" bit. Sorry.
        return (IncomingType*)( (POINTER_SIZE_INT)incoming & ~3 );
    }

    //return first conatrint of any type except IncomingType
    Constraint *firstOthers() {
        return others;
    }

    //return first CT_EXPECTED_TYPE constraint
    ExpectedType *firstExpected() {
        return (ExpectedType*)Constraint::next(others, CT_EXPECTED_TYPE);
    }

    //return first CT_GENERIC constraint
    GenericCnstr *firstGenericCnstr() {
        return (GenericCnstr*)Constraint::next(others, CT_GENERIC);
    }

    //return first (and the only) CT_ARRAY2REF constraint
    ArrayCnstr *firstArrayCnstr() {
        return (ArrayCnstr*)Constraint::next(others, CT_ARRAY2REF);
    }

    //clean-up
    void init() {
        incoming = 0;
        others = 0;
    }

    //add incoming type with the 'value' value
    void newIncomingType(Memory *mem, SmConstant value) {
        IncomingType *in = (IncomingType *)mem->malloc(sizeof(IncomingType));

        POINTER_SIZE_INT mask = (POINTER_SIZE_INT)incoming & 3;
        incoming = (IncomingType *) ((POINTER_SIZE_INT)incoming & ~3);

        in->nxt = value == SM_BOGUS ? 0 : incoming;
        //in->type = CT_INCOMING_VALUE;
        in->value = value;

        incoming = in;

        incoming = (IncomingType *) ((POINTER_SIZE_INT)incoming | mask);
    }

    //add expected type with the 'value' value
    void newExpectedType(Memory *mem, SmConstant value) {
        newConstraint(mem, CT_EXPECTED_TYPE)->value = value.c;
    }

    Constraint *newConstraint(Memory *mem, int type) {
        Constraint *o = (Constraint *)mem->malloc(sizeof(Constraint));

        o->nxt = others;
        o->type = (ConstraintType)type;

        others = o;
        return o;
    }

    //add generic constraint ('this' is assignable to 'to')
    void newGenericConstraint(Memory *mem, StackmapElement_5 *to) {
        newConstraint(mem, CT_GENERIC)->variable = to;
    }

    //add generic constraint ('this' is an array, which element is assignable to 'to')
    void newArrayConversionConstraint(Memory *mem, StackmapElement_5 *to) {
        assert(!firstArrayCnstr());
        newConstraint(mem, CT_ARRAY2REF)->variable = to;
    }

    // return 'modified' flag for the stackmap. the flag is stored in the first bit of the 'incoming' pointer
    // "modified" is about subroutines: you have to track which locals were changed
    int isJsrModified() {
        return (int)(POINTER_SIZE_INT)incoming & 1;
    }

    //set 'modified' flag for the stackmap. the flag is stored in the first bit of the 'incoming' pointer
    // "modified" is about subroutines: you have to track which locals were changed
    void setJsrModified() {
        incoming = (IncomingType *) ((POINTER_SIZE_INT)incoming | 1);
    }

    //clear 'modified' flag for the stackmap. the flag is stored in the first bit of the 'incoming' pointer
    // "modified" is about subroutines: you have to track which locals were changed
    void clearJsrModified() {
        incoming = (IncomingType *) ((POINTER_SIZE_INT)incoming & ~1);
    }
};

//WorkMapElement structure represent an element of the workmap vector -- vector of the derived types
//a type might be either constant (or known) (e.g. if some previous instruction has put something on stack or locals)
//or sub-definite (e.g. if we've recently passed a branch target and don't know which types were on stack or locals)
struct WorkmapElement_5 {
    //value. two low bits a used to store flags
    union {
        _SmConstant const_val;        //either a constant (known-type)
        StackmapElement_5 *var_ptr;   //or a variable (sub-definite type)
    };

    //is it a sub-definite (not constant) type?
    int isVariable() {
        assert(const_val != SM_NONE);
        return !((POINTER_SIZE_INT)var_ptr & 1);
    }

    //get value for the constant (known) verification type
    SmConstant getConst() {
        return const_val;
    }

    //get variable representing sub-definite verification type
    StackmapElement_5 *getVariable() {
        return (StackmapElement_5 *) ((POINTER_SIZE_INT)var_ptr & ~3);
    }

    //when we need to compae to some unmergable type we don;t need to interate thru the list
    //also used to assert that an IncomingValue constraint exists
    SmConstant getAnyPossibleValue() {
        SmConstant ret = isVariable() ? getVariable()->getAnyIncomingValue() : (SmConstant) const_val;
        assert(ret != SM_NONE);
        return ret;
    }

    // return 'modified' flag for the workmap element. the flag is stored in the second bit of the union
    //"modified" is about subroutines: you have to track which locals were changed
    //it's easier to think of all the constants as "modified"
    int isJsrModified() {
        return (int)(POINTER_SIZE_INT)var_ptr & 3;
    }

    // set 'modified' flag for the workmap element. the flag is stored in the second bit of the union
    void setJsrModified() {
        if( isVariable() ) {
            var_ptr = (StackmapElement_5*)((POINTER_SIZE_INT)var_ptr | 2);
        }
    }
};

//WorkmapElement type with some constructors
struct _WorkmapElement_5 : WorkmapElement_5 {
    _WorkmapElement_5(WorkmapElement_5 other) {
        const_val = other.const_val;
    }

    _WorkmapElement_5(StackmapElement_5 *s) {
        var_ptr = s;
        if( s->isJsrModified() ) {
            setJsrModified();
        }
    }

    _WorkmapElement_5(SmConstant c) {
        const_val = c;
    }
};

//possible flag value
static const short FF_ISWORKMAP = 1;

//structure for maintaining subroutine-specific data
//until subroutine is passed with the second (dataflow) pass we record to the wait list all JSR instructions
//calling this subroutine. Once the subroutine is over we continue 2nd pass for each wait-listed instruction
//see vf_Context_Base::SubroutineDone
struct SubroutineData {
    Address caller;         //first JSR instruction that called this subroutine
    short retCount;         //number of ret instructions for this subroutine
    U_8  subrDataflowed;   // =1 if dataflow pass for the subroutine is over
};

//Store various data for the given instruction. Possible data are: StackMap vector, WorkMap vector,
//Subroutine-specific data
//for a single instruction it might be either
// 1) no data
// 2) workmap only
// 3) stackmap only
// 4) stackmap and subroutine data. in this case two PropsHead structures are created the first one for the StackMap,
//    it's 'next' points to the second PropsHead containing Subroutine info. In this case second PropsHead keeps 0xFFFF
//    instead of 'instr'
// the list is used to organize storing Props as a HashTable
struct PropsHead_5 : public PropsHeadBase {
    typedef MapHead<WorkmapElement_5> WorkmapHead;
    typedef MapHead<StackmapElement_5> StackmapHead;

    // really one bit is used: FF_ISWORKMAP. TODO: merge with (Stack|Work)map->flags
    unsigned short instr_flags; 

    //actual properties
    union {
        WorkmapHead workmap;
        StackmapHead stackmap;
    };

    //get workmap stored here
    WorkmapHead *getWorkmap() {
        assert(is_workmap());
        return &workmap;
    }

    //get stackmap stored here
    StackmapHead *getStackmap() {
        assert(!is_workmap());
        return &stackmap;
    }

    //get subroutine data stored here
    SubroutineData *getSubrData(int el_cnt) {
        assert(instr == 0xFFFF);
        return (SubroutineData *) &stackmap.elements[el_cnt];
    }

    //is it a workmap?
    int is_workmap() {
        return instr_flags & FF_ISWORKMAP;
    }

    //set 'is workmap' flag
    void set_as_workmap() {
        instr_flags |= FF_ISWORKMAP;
    }

    //clear flag
    void clearInstrFlag(short flag) {
        instr_flags &= ~flag;
    }
};

#endif
